在這篇文章中,將會介紹 JavaScript 的 Runtime Environment,讓讀者更了解 JS 的執行過程。
說到 JS 的 Runtime Environment,那就肯定是要搬出這張圖去做解釋!
左邊的區塊就是 JS 引擎,在引擎內部會有 Call Stack,但是單靠引擎內部運作的話,程式一次只會做一件事,一個任務執行完後才會執行其他任務,也就是同步(Synchronous)。
但瀏覽器,以 Chrome 為例,是**多進程(multi processes) & 多執行緒(multi threads)**的,因此可以同時執行好幾個任務,詳細 Chrome 內部有哪些可參考底下兩個連結:
Inside look at modern web browser
其中,那些瀏覽器執行緒裡面處理的事件/任務,像是 setTimeout,XMLHttpRequest等就屬於非同步事件,你可能需要等待一小段時間才能得到 response 的任務。
假如非同步事件會和同步事件都一起放進 Call stack,那麼就會造成阻塞,比如我們發出一個 HTTP 的請求,那麼在完成回傳 response 之前,就會因為 JS 單執行緒的特性,我們在網頁上都無法進行其它動作。
而在 JavaScript Runtime Environment,透過 Event Loop、Task Queue 能讓 JS 順利的執行非同步事件。在 JavaScript Runtime Environment 內部,主要分成了五個部分:
之前已經有介紹過 JS 引擎了,所以就跳過,以下說明另外四個部分:
瀏覽器提供的 API,包括:
它是一個佇列的資料結構,當 Web API 的非同步事件觸發時,其 callback function 會進入到 Task Queue 裡面,等 Call stack 裡面沒有其它任務才會進入到 Call stack 執行。
主要存放 Macrotasks,像是 setTimeout、setImmediate、UI 互動事件。
Macrotasks 歸類於瀏覽器提供
存放 Microtasks,例如 Promise .then/catch/finally
函式、MutationObserver、process.nextTick。
Microtasks 的函式偏向是 V8 引擎提供的 API
透過 Event loop,可以判斷是否要將非同步任務加到 Call Stack 執行,其監測步驟如下:
在以下的網站中,將整個 Runtime Environment 視覺化並附上了一段程式碼,有興趣的讀者可以進去網站並執行程式碼。
以下兩道題目讀者可以練習看看,如果有不清楚或是回答錯誤的話,可以將前面的內容再多看一下喔~
以下有五個 console.log,試著判斷它們印出的先後順序:
setTimeout(() => {
console.log('I will be printed out first.');
}, 0);
const promiseExample = new Promise(function (resolve) {
resolve('But I still faster than you, Mr.SetTimeout. I am a microtask.')
}).then((data) => {
console.log(data);
})
function mockFunc() {
console.log('Shut up! I am a synchronous task so I will run first.');
}
mockFunc();
也許從對話就能看出結果了? XD
這段程式碼會印出什麼?
console.log("A");
setTimeout(function () {
console.log("B");
}, 0);
while(true) {} // 死循環
答案是只會印出 A,由於要等到 Call stack 的同步任務都完成,非同步的任務才會透過 event loop 進到 Call stack 執行,但 while loop 一直不會執行完,所以就不會印出 B。
Day5 [JavaScript 基礎] Event Loop 機制
Demystifying Asynchronous JavaScript — Event Loop, Call stack, Task Queue, and More!
以下 ChatGPT 回答:
Microtasks(微任務)會優先於 Task Queue(宏任務)的原因是與語言設計和性能優化相關,以下是詳細的解釋:
微任務(Microtasks)是一類執行優先級較高的任務,通常用於處理當前執行上下文中的後續操作。常見的 Microtasks 包括:
Promise.then
和 catch
回調queueMicrotask
API而宏任務(Macrotasks)則是執行優先級較低的任務,例如:
setTimeout
和 setInterval
回調setImmediate
(Node.js 特有)事件循環的核心原則是在執行每一個宏任務之前,會清空所有的微任務隊列。執行順序如下:
微任務的目的是讓代碼能夠快速處理細微的後續工作,而不會中斷當前的執行上下文。例如,Promise
的回調允許在異步操作完成後立即執行後續邏輯,而不需要等待下一個宏任務。
如果 Microtasks 必須等到下一個 Macrotask 執行後才執行,這可能會導致不必要的延遲。例如:
setTimeout(() => console.log('Macrotask'), 0);
Promise.resolve().then(() => console.log('Microtask'));
按照 Microtasks 先執行的原則,Promise
回調中的微任務會比 setTimeout
的宏任務更早執行。輸出結果為:
Microtask
Macrotask
如果改成先執行宏任務,微任務將不得不等到下一個事件循環才執行,影響代碼的響應性。
Microtasks 被設計成與當前執行上下文緊密相關。這樣的設計可以避免在處理異步操作時,開發者不得不手動管理細小的任務優先級。
Microtasks 允許在執行下一個宏任務之前,快速處理一批高優先級任務,從而減少宏任務之間的干擾,提升應用的整體性能和穩定性。
Microtasks 優先於 Macrotasks 的執行順序是為了在語言層面上平衡異步操作的性能和易用性,確保快速響應和一致的執行順序,從而提供更好的開發者體驗。